<?php
declare(strict_types=1);

namespace app\common\support;

use app\common\http\ResponseCode;
use DateTimeImmutable;
use DateTimeZone;
use Lcobucci\Clock\SystemClock;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Encoding\JoseEncoder;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Token\Parser;
use Lcobucci\JWT\Validation\Constraint\IdentifiedBy;
use Lcobucci\JWT\Validation\Constraint\IssuedBy;
use Lcobucci\JWT\Validation\Constraint\PermittedFor;
use Lcobucci\JWT\Validation\Constraint\StrictValidAt;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
use think\Exception;

class Token
{
    // 过期缓存储存
    const DELETE_TOKEN = 'delete_token';

    protected string $accessToken = ''; // token信息

    // 存储字段名称
    protected array $storages = [
        'admin_id' => '',
        'is_root' => false,
    ];

    // 配置属性
    protected array $config = [
        'id' => 'uS224PRas1I*i3YK11HLr',      // token的唯一标识
        'issuer' => 'admin-api.lhp.com',        // 签发人
        'audience' => '',                       // 接收人 魔术方法 __construct 动态设置
        'sign' => 'lhp',                        // 签名密钥
        'expire' => 3600 * 24                   // 有效期  86400
    ];

    public function __construct()
    {
        $this->config['audience'] = !app()->runningInConsole()
            ? md5(request()->server('HTTP_USER_AGENT'))
            : '';
    }

    /**
     * 设置配置数组
     *
     * @param string|array $name 配置名称或配置数组
     * @param string|int $value 配置值
     *
     * @return $this
     */
    public function withConfig($name, string|int $value = ''): Token
    {
        if (is_array($name)) {
            foreach ($name as $k => $v) {
                $this->withConfig($k, $v);
            }
            return $this;
        }

        if (isset($this->config[$name])) {
            $this->config[$name] = $value;
        }

        return $this;
    }

    public function withConfigs(array $configs): Token
    {
        foreach ($configs as $name => $value) {
            $this->withConfig($name, $value);
        }
        return $this;
    }

    /**
     * 设置缓存数据
     *
     * @param string|array $name 缓存名称或缓存数组
     * @param mixed $value 缓存值
     *
     * @return $this
     */
    public function withStorages($name, $value = ''): Token
    {
        if (is_array($name)) {
            foreach ($name as $k => $v) {
                $this->withStorages($k, $v);
            }
            return $this;
        }

        $this->storages[$name] = $value;
        return $this;
    }
    /**
     * 设置请求中的token
     * 
     * @throws Exception
     */
    public function withToken(string $token = ''): Token
    {
        $this->accessToken = $token;

        if (empty($this->accessToken) || count(explode('.', $this->accessToken)) !== 3) {
            throw new Exception('token格式错误', ResponseCode::ACCESS_TOKEN_ERROR);
        }

        return $this;
    }
    /**
     * 设置请求中的token
     * 
     * @throws Exception
     */
    public function withRequestToken(string $authorization = ''): Token
    {
        $authorization = $authorization ?: request()->header('Authorization');

        if (empty($authorization)) {
            throw new Exception('token不能为空', ResponseCode::ACCESS_TOKEN_ERROR);
        }

        $parts = explode(' ', $authorization);

        if (count($parts) !== 2 || $parts[0] !== 'Bearer') {
            throw new Exception('token格式错误', ResponseCode::ACCESS_TOKEN_ERROR);
        }

        $this->accessToken = $parts[1];

        if (empty($this->accessToken) || count(explode('.', $this->accessToken)) !== 3) {
            throw new Exception('token格式错误', ResponseCode::ACCESS_TOKEN_ERROR);
        }

        return $this;
    }

    /**
     * 获取请求中的token
     */
    public function getRequestToken(): string
    {
        return $this->accessToken;
    }

    /**
     * 解析JWT token
     * 
     * @throws Exception
     */
    protected function getJWTToken(): \Lcobucci\JWT\Token\Plain
    {
        try {
            $parser = new Parser(new JoseEncoder());
            return $parser->parse($this->accessToken);
        } catch (\Exception $e) {
            throw new Exception('token解析错误', ResponseCode::ACCESS_TOKEN_ERROR);
        }
    }

    /**
     * 获取JWT配置
     */
    protected function getConfiguration(): Configuration
    {
        $config = Configuration::forSymmetricSigner(
            new Sha256(),
            InMemory::plainText($this->config['sign'])
        );

        $clock = SystemClock::fromSystemTimezone();

        $config->setValidationConstraints(
            new IssuedBy($this->config['issuer']),
            new PermittedFor($this->config['audience']),
            new IdentifiedBy($this->config['id']),
            new StrictValidAt($clock)
        );

        return $config;
    }

    /**
     * 生成token
     */
    public function getToken(): string
    {
        $config = $this->getConfiguration();
        $now = new DateTimeImmutable();

        $builder = $config->builder();

        $token = $builder
            ->issuedBy($this->config['issuer'])
            ->permittedFor($this->config['audience'])
            ->identifiedBy($this->config['id'])
            ->issuedAt($now)
            ->canOnlyBeUsedAfter($now->modify('-1 second'))
            ->expiresAt($now->modify("+{$this->config['expire']} second"));

        // 存储自定义数据
        foreach ($this->storages as $key => $val) {
            $token = $token->withClaim($key, $val);
        }

        return $token->getToken($config->signer(), $config->signingKey())->toString();
    }

    /**
     * 注销token
     * 
     * @throws Exception
     */
    public function delete(?string $token = null)
    {
        $deleteTokens = cache(self::DELETE_TOKEN) ?: [];
        $deleteTokens[] = $token ?: $this->accessToken;
        cache(self::DELETE_TOKEN, $deleteTokens);
    }

    /**
     * 获取token中的声明
     * 
     * @throws Exception
     */
    public function getClaim(string $name = 'admin_id')
    {
        return $this->getJWTToken()->claims()->get($name);
    }
    /**
     * 获取token中的声明
     * 
     * @throws Exception
     */
    public function getClaimAll()
    {
        return $this->getJWTToken()->claims()->all();
    }
    /**
     * 验证token
     * 
     * @return $this
     * @throws Exception
     */
    public function validate()
    {
        if (empty($this->accessToken)) {
            throw new Exception('token解析错误', ResponseCode::ACCESS_TOKEN_ERROR);
        }

        // 检查已注销的token
        $deletedTokens = cache(self::DELETE_TOKEN) ?: [];
        if (in_array($this->accessToken, $deletedTokens)) {
            throw new Exception('token已注销', ResponseCode::ACCESS_TOKEN_ERROR);
        }

        try {
            $token = $this->getJWTToken();
            $config = $this->getConfiguration();

            $config->validator()->assert($token, ...$config->validationConstraints());

            // 适配 v5.4 的签名验证方式
            $signer = new Sha256();
            if (
                !$signer->verify(
                    $token->signature()->hash(), // 获取签名的哈希值
                    $token->payload(),
                    InMemory::plainText($this->config['sign'])
                )
            ) {
                throw new Exception('签名验证失败', ResponseCode::ACCESS_TOKEN_ERROR);
            }

            return $this;
        } catch (RequiredConstraintsViolated $e) {
            $violations = array_map(function ($violation) {
                return $violation->getMessage();
            }, $e->violations());

            throw new Exception('token验证失败: ' . implode('; ', $violations), ResponseCode::ACCESS_TOKEN_ERROR);
        } catch (\Exception $e) {
            throw new Exception('token解析错误: ' . $e->getMessage(), ResponseCode::ACCESS_TOKEN_ERROR);
        }
    }
}